
package hasthi.manager.gui;

import static hasthi.manager.gui.ResourceNode.NodeType.ComputeNode;
import static hasthi.manager.gui.ResourceNode.NodeType.Coordinator;
import static hasthi.manager.gui.ResourceNode.NodeType.Host;
import static hasthi.manager.gui.ResourceNode.NodeType.Manager;
import static hasthi.manager.gui.ResourceNode.NodeType.Parent;
import static hasthi.manager.gui.ResourceNode.NodeType.Root;
import static hasthi.manager.gui.ResourceNode.NodeType.Service;
import static hasthi.manager.gui.ResourceNode.NodeType.TransientService;
import hasthi.common.HasthiException;
import hasthi.common.HasthiException.FaultCode;
import hasthi.common.constants.HasthiConstants;
import hasthi.common.constants.WSDMConstants;
import hasthi.manager.ResourceOutline;
import hasthi.manager.ManagementRingDocument.ManagementRing;
import hasthi.manager.ManagementRingDocument.ManagementRing.ManagerData;
import hasthi.metamodel.ResourceModel;
import hasthi.metamodel.ResourceModelFactory;
import hasthi.metamodel.resource.ManagedResource;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import javax.swing.JTree;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This is the TreeModel used by the Tree that shows the resources in the Management GUI. It has a resource model
 * as it back end and changes to Resource are represented in the tree model.
 * TODO how to handle adding and removing resources
 * 
 * @author Srinath Perera(hperera@cs.indiana.edu)
 */
public class ResourceTreeModel implements TreeModel,TreeSelectionListener{

    private Log log = LogFactory.getLog(HasthiConstants.LOGGER_NAME);
    private Vector<TreeModelListener> treeModelListeners = new Vector<TreeModelListener>();
    
    private ResourceNode root;
    private ResourceNode services;
    private ResourceNode hosts;
    private ResourceNode tranisentServices;
    private ResourceNode computeNodes;
    private ResourceNode managers;
    private ResourcePannel resourcePannel;
    private ResourceModel resourceModel;
    private UIContext uicontext;
    
    private Map<String,ResourceNode> hostMap = new HashMap<String, ResourceNode>();
    private Map<String,ResourceNode> resourceNodeMap = new HashMap<String, ResourceNode>();
    
    /**
     * @param resourceModel
     * @param resourcePannel
     * @throws HasthiException
     */
    public ResourceTreeModel(ResourcePannel resourcePannel,UIContext uicontext) throws HasthiException{
        this.resourcePannel = resourcePannel;
        this.uicontext = uicontext;
        root = new ResourceNode("root",Root);
        tranisentServices = new ResourceNode("TranisentServices",Parent);
        root.addChild(tranisentServices);
        
        hosts = new ResourceNode("Hosts",Parent);
        root.addChild(hosts);
        
        services = new ResourceNode("Services",Parent);
        root.addChild(services);
        
        computeNodes = new ResourceNode("ComputeNodes",Parent);
        root.addChild(computeNodes);
        
        managers = new ResourceNode("Managers",Parent);
        root.addChild(managers);
        
        try {
            resourceModel = ResourceModelFactory.getExternalResourceModel(uicontext);
            loadAllResources();
        } catch (HasthiException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public void loadAllResources() throws HasthiException{
        tranisentServices.reset();
        services.reset();
        computeNodes.reset();
        computeNodes.reset();
        managers.reset();
        
        if(hosts.getChildren() != null){
            for(ResourceNode node:hosts.getChildren()){
                node.reset();
            }
        }
        ManagedResource[] resourceList;
        
        try {
            if(resourceModel == null){
                resourceModel = ResourceModelFactory.getExternalResourceModel(uicontext);
            }
            resourceList = resourceModel.getAllResources();
        } catch (HasthiException e) {
            log.trace(e);
            try {
                resourceModel.reload();
            } catch (InterruptedException e1) {
               throw new HasthiException("Interuppted while trying to find Coordinator ", e1, FaultCode.LocalError);
            }
            resourceList = resourceModel.getAllResources();
        }
        for(ManagedResource resource:resourceList){
            ManagedResource.ResourceType type = ManagedResource.ResourceType.valueOf(resource.getCategory());
            ResourceNode node;
            //System.out.println("Process "+resource.getQName());
            switch(type){
                case TransientService:
                    node = new ResourceNode(resource,TransientService);
                    tranisentServices.addChild(node);
                    addToHost(node, resource);
                    break;
                case Host:
                    ResourceNode resourceNode = hostMap.get(resource.getName());
                    if(resourceNode == null){
                        node = new ResourceNode(resource,Host);
                        hostMap.put(resource.getName(), node);
                        hosts.addChild(node);  
                    }else{
                        if(resourceNode.getResource() == null){
                            resourceNode.setResource(resource);
                        }
                        node = resourceNode;
                    }
                    break;
                case ComputeNode:
                    node = new ResourceNode(resource,ComputeNode);
                    computeNodes.addChild(node);  
                    break;
                default:
                    node = new ResourceNode(resource,Service);
                    services.addChild(node);
                    addToHost(node, resource);
            }
            resourceNodeMap.put(node.toString(), node);
        }
        
        ManagementRing ringData = ResourceModelFactory.getManagerRingState(uicontext);
        //Add the Mangers information to the table
        if(ringData != null){
           managers.addChild(new ResourceNode(ringData.getCoordinator().getSoapAddress() + "(Coordinator)",Coordinator));
           ManagerData[] managersList = ringData.getManagerDataArray();
           if(managers != null){
               uicontext.getManagers().clear();
               for(ManagerData manager:managersList){
                   ResourceNode managerNode = new ResourceNode(manager.getManagerAddress().getSoapAddress(),Manager);
                   managers.addChild(managerNode);
                   uicontext.getManagers().add(manager);
                   ResourceOutline[] resources = manager.getResourceDataArray();
                   if(resources != null){
                       for(ResourceOutline resourceData:resources){
                           ResourceNode resourceNode = resourceNodeMap.get(QName.valueOf(resourceData.getName()).getLocalPart());
                           if(resourceNode != null){
                               managerNode.addChild(resourceNode);    
                           }else{
                               log.info("Resource "+ resourceData.getName() + " not found among reosurces");
                           }
                       }
                   }
               }
           }
        }
    }
    
    
    
    private void addToHost(ResourceNode node,ManagedResource resource) throws HasthiException{
        String hostName = (String)resource.getProperty(WSDMConstants.HOSTNAME_PARAM.getLocalPart());
        ResourceNode hostNode = hostMap.get(hostName);
        if(hostNode == null){
            hostNode = new ResourceNode(hostName,Host);
            hostMap.put(hostName, hostNode);
            hosts.addChild(hostNode);
        }
        hostNode.addChild(node);
    }
    

    public void removeTreeModelListener(TreeModelListener l) {
        treeModelListeners.remove(l);
    }
    public void addTreeModelListener(TreeModelListener l) {
        treeModelListeners.add(l);
    }

    public Object getChild(Object parent, int index) {
        return ((ResourceNode)parent).getChild(index);
    }

    public int getChildCount(Object parent) {
        Collection<ResourceNode> children = ((ResourceNode)parent).getChildren();
        return children == null?0:children.size();
    }

    public int getIndexOfChild(Object parent, Object child) {
        return ((ResourceNode)parent).getIndexOfChild(child);
    }

    public Object getRoot() {
        return root;
    }

    public boolean isLeaf(Object node) {
        return getChildCount(node) == 0 && ((ResourceNode)node).getResource() != null;
    }

    public void signalTreeChange(){
        TreeModelEvent e = new TreeModelEvent(this, new Object[]{root});
        for(TreeModelListener l:treeModelListeners){
            l.treeStructureChanged(e);
        }
    }
    
    
    public void valueForPathChanged(TreePath path, Object newValue) {
        throw new UnsupportedOperationException();
    }

    public void valueChanged(TreeSelectionEvent e) {
        //resourcePannel.setResource(e.getSource().)
        ResourceNode obj = (ResourceNode)((JTree)e.getSource()).getLastSelectedPathComponent();
        if(obj != null && obj.getResource() != null){
            resourcePannel.setResource(obj.getResource());
            resourcePannel.repaint();
        }
    }
}

